﻿/*
输入：
7 11
1 2 1
1 3 1
1 5 2
2 6 1
2 4 2
2 3 2
3 4 1
4 5 1
5 6 2
5 7 1
6 7 1
输出：
6
*/
// N个顶点，K条边
string[] NK = Console.ReadLine().Split(" ");
int N = int.Parse(NK[0]);
int K = int.Parse(NK[1]);

// 边排序
List<(int, int, int)> edges = new List<(int, int, int)>();
for (int i = 0; i < K; i++)
{
    string[] stv = Console.ReadLine().Split(" ");
    int s = int.Parse(stv[0]);
    int t = int.Parse(stv[1]);
    int v = int.Parse(stv[2]);
    edges.Add((s, t, v));
}
edges.Sort((e1, e2) =>
{
    (int _, int _, int v1) = e1;
    (int _, int _, int v2) = e2;
    return v1.CompareTo(v2); // 升序
});

// 构造并查集
int[] father = new int[N+1];
for (int i = 0; i <= N; i++) // 初始化
{
    father[i] = i;
}

int find(int[] father, int u) // 寻找根节点，路径压缩
{
    return father[u] == u ? u : father[u] = find(father, father[u]);
}

bool isSame(int[] father, int u, int v) // 判断是否属于同一集合
{
    u = find(father, u);
    v = find(father, v);
    return u == v;
}

void Join(int[] father, int u, int v) // 将两个元素合并成一个集合
{
    u = find(father, u);
    v = find(father, v);
    if (isSame(father, u, v)) return;
    father[u] = v;
}

// Kruskal算法
int cost = 0;
while (edges.Count > 0)
{
    (int s, int t, int v) = edges[0];
    edges.RemoveAt(0);
    
    // 如果属于同一集合
    if (isSame(father, s, t)) continue;
    
    Join(father,s,t);
    cost += v;
}
Console.WriteLine(cost);